import React, {useState, useMemo, useEffect, useCallback, memo} from 'react';
import {Button} from "antd";

/**
 *
 * useEffect 会在组件宣软完毕后执行和渲染无关的副作用。
 * useMemo: 会在组件首次加载和重新渲染期间执行，执行函数需要和渲染相关的。
 * useCallBack: 会在渲染期间执行，返回一个函数。
 *
 */

function Intro() {
  return (
    <div>
      <ul>
        <li>memo：优化函数组件的重复渲染行为，针对的是一个组件。</li>
        <li>useEffect：会在组件渲染完毕后执行，和渲染无关的副作用。</li>
        <li>useMemo：会在在组件首次加载和重渲染期间执行，执行的函数需要和渲染相关的。</li>
        <li>useCallback：会在渲染期间执行，返回一个函数。</li>
      </ul>
      <h3>一、区别</h3>
      <ol>
        <li>useCallback 和 useMemo 的参数跟 useEffect 一致，他们之间最大的区别有是 useEffect 会用于处理副作用，而前两个hooks不能。</li>
        <li>useCallback 和 useMemo 都会在组件第一次渲染的时候执行，之后会在其依赖的变量发生改变时再次执行。</li>
        <li>并且这两个hooks都返回缓存的值，useMemo 返回缓存的 变量，useCallback 返回缓存的 函数。</li>
        <li>如果没有提供依赖项数组，useMemo 在每次渲染时都会计算新的值。</li>
        <li>React.useMemo将调用fn函数并返回其结果，而React.useCallback将返回fn函数而不调用它：useCallback(fn, deps) 相当于 useMemo(() =&gt; fn,
          deps)。
        </li>
        <li>useMemo缓存的变量所代表的意义不是组件的state，组件的state改变会触发组件重渲染，而useMemo缓存的变量可能会因state改变而改变，是“被动的”；</li>
      </ol>
    </div>

  )
}

function Name() {
  const [name, setName] = useState('')
  const getName = () => {
    console.log('getName-----')
    if (name === '') {
      return "张三"
    } else {
      return name
    }
  }
  useEffect(() => {
    console.log('Name');
  }, [])

  return (
    <div className="App">
      <p>名称：</p>
      <p>{getName()}</p>
      <p onClick={() => setName('哈哈')}>
        <button>改变Name</button>
      </p>
    </div>
  );
}


function NameMemo() {
  const [name, setName] = useState('')
  const [age, setAge] = useState(0)
  const getName = useMemo(() => {
    console.log('useMemo-----')
    if (name === '') {
      return "张三"
    } else {
      return name
    }
  }, [name])
  const getAge = () => {
    console.log('getAge-----')
    return age
  }
  const handleName = () => {
    setName(`${name}${age}`)
  }
  const handleAge = () => {
    setAge(age + 1)
  }
  useEffect(() => {
    console.log('NameMemo');
  }, [])

  return (
    <div className="App">
      <p>名称：</p>
      <p>{getName}</p>
      <p onClick={handleName}>
        <button>改变Name</button>
      </p>
      <p>年龄：</p>
      <p>{getAge()}</p>
      <p onClick={handleAge}>
        <button>改变年龄</button>
      </p>
    </div>
  );
}


/**父组件**/
const Parent = () => {
  const [parentState, setParentState] = useState(0); //父组件的state
  //需要传入子组件的函数
  const toChildFun = (n) => {
    console.log("需要传入子组件的函数");
    setParentState(n);
  }
  useEffect(() => {
    console.log('Parent');
  }, [])
  return (<div>
    <Button onClick={() => setParentState(val => val + 1)}>
      点击我改变父组件中与Child组件无关的state
    </Button>
    //将父组件的函数传入子组件
    <Child fun={toChildFun}></Child>
  </div>)
}
/**被memo保护的子组件**/
const Child = memo(({toChildFun}) => {
  console.log("我被打印了就说明子组件重新构建了")
  return <div>
    <button onClick={() => {
      toChildFun(2)
    }}>点我
    </button>
  </div>
})

/**父组件CallBack**/
const ParentCallBack = () => {
  const [parentState, setParentState] = useState(0); //父组件的state
  //需要传入子组件的函数
  //只有这里不一样
  const toChildFun = useCallback((n) => {
    console.log("需要传入子组件的函数");
    setParentState(n)
  }, [])

  useEffect(() => {
    console.log('ParentCallBack');
  }, [])
  return (
    <div>
      <Button onClick={() => setParentState(val => val + 1)}>
        点击我改变父组件中与Child组件无关的state
      </Button>
      //将父组件的函数传入子组件
      <Child fun={toChildFun}></Child>
    </div>
  )
}

const LearnReactMemo = () => {
  useEffect(() => {
    console.log('LearnReactMemo')
  }, [])
  return (
    <div>
      <Intro></Intro>
      正常模式：
      <Name/>
      <br/>
      使用UseMemo:
      <NameMemo/>

      <br/>
      <hr/>
      正常模式：
      <Parent></Parent>
      使用UseCallBack:
      <ParentCallBack></ParentCallBack>
      <ul>
        <li><strong>useCallBack不要每个函数都包一下，否则就会变成反向优化，useCallBack本身就是需要一定性能的</strong></li>
        <li><strong>useCallBack并不能阻止函数重新创建,它只能通过依赖决定返回新的函数还是旧的函数,从而在依赖不变的情况下保证函数地址不变</strong></li>
        <li><strong>useCallBack需要配合React.memo使用</strong></li>
      </ul>
    </div>
  )
}

export default LearnReactMemo;
